/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is JsWs.
 *
 * The Initial Developer of the Original Code is
 * Chris Winberry.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */


/**
 * @class Exception class for request parsing
 * @constructor
 * @base ExBase
 * @param {String} msg Description of exception
 * @param {Number} code (optional) Error code
 * @param {Exception} ex (optional) An earlier exception to wrap
 * @param {Arguments} args (optional) Arguments array of a functions
 * @see Request
 */
function ExHttpParseError (msg, code, ex) { ExHttpParseError.baseConstructor.call(this, msg, code, ex); }
SubClass(ExBase, ExHttpParseError);


/**
 * @class Class to represent an HTTP request
 * @constructor
 */
function Request () {
	/**
	 * Headers collection
	 * @type Hash
	 */
	this.headers = new Array();
	this.headers['content-length'] = 0;
	this.headers['content-type'] = '';
	/**
	 * Cookies collection
	 * @type Cookie[]
	 */
	this.cookies = new Array();
	/**
	 * Querystring (GET) parameters
	 * @type Querystring
	 */
	this.querystring = new Array();
	this.watch("querystring", function (property, oldVal, newVal) { return(new Querystring(newVal)); });
	/**
	 * Form (POST) parameters
	 * @type Querystring
	 */
	this.form = new Array();
	this.watch("form", function (property, oldVal, newVal) { return(new Querystring(newVal)); });
	/**
	 * Request methods
	 * @type String
	 */
	this.method = "GET";
	/**
	 * Complete path/querystring of the resource requested
	 * @type String
	 */
	this.url = "/";
	/**
	 * HTTP version specified by the client
	 * @type String
	 */
	this.httpVersion = "HTTP/1.0";
	/**
	 * Path to the resource requested
	 * @type String
	 */
	this.path = "/";
	/**
	 * Complete, unparsed request
	 * @type String
	 */
	this.raw = "";
	/**
	 * Complete, unparsed headers
	 * @type String
	 */
	this.rawHeaders = "";
	/**
	 * Complete, unparsed querystring
	 * @type String
	 */
	this.rawQuerystring = "";
	/**
	 * Complete, unparsed body
	 * @type String
	 */
	this.rawBody = "";

	/** @private */
	this.headersComplete = false;
}


/**
 * Attempts to parse a raw request. Returns false until it is successful.
 * @param {String} sData Data from the incoming request. Data is dealt with
 *  in a chunked fashion (<i>Request</i> maintains all the data internally) so if
 *  calling <i>parse()</i> more than once, only new data should be passed each time. 
 * @returns True/False indicating whether the request has been completely parsed
 * @type Boolean
 */
Request.prototype.parse = function (sData) {
	if (sData != null && sData.length) {
		this.raw += sData;
	}
	if (!this.raw.match(/(\r\n\r\n|\r\r|\n\n)/)) {
		return(false);
	}

	if (!this.headersComplete) {
		[this.rawHeaders, this.rawBody] = this.raw.split(/(?:\r\n\r\n|\r\r|\n\n)/);

		var headers = this.rawHeaders.split(/(?:\r\n|\r|\n)/); // TODO: handle multiline headers
		if (!headers.length) { throw(new ExHttpParseError("Unable to find headers in request [[" + this.rawHeaders + "]]")); }

		[
			this.method,
			this.url,
			this.httpVersion
			] = headers[0].split(/\s/);
		if (this.method == null || this.url == null || this.httpVersion == null) {
 			throw(new ExHttpParseError("Unable to find request method and url [["+ headers[0] +"]]"));
		}
		this.method = this.method.toLowerCase();

		[this.path, this.rawQuerystring] = this.url.split(/\?/);
		this.querystring = this.rawQuerystring;

		for (var i = 1; i < headers.length; i++) {
			var pieces = headers[i].split(/\:\s*/);
			if (pieces.length < 2) { JsWs.log("Error on header split: " + headers[i]); continue; }
			this.headers[pieces.shift().toLowerCase()] = pieces.join(':');
		}

		this.cookies = Cookie.parse(this.headers["cookie"]);

		this.headersComplete = true;
	}
	else {
		this.rawBody += sData;
	}

	if (
		(this.method == "post")
		&&
		(this.headers['content-length'] == this.rawBody.length)
		&&
		(this.headers['content-type'].indexOf("application/x-www-form-urlencoded") > -1)
		){
		this.form = this.rawBody;
	}

	return(
		this.headersComplete &&
		(
			this.headers['content-length'] == null
			||
			this.headers['content-length'] == this.rawBody.length
			)
		);
}


/**
 * Runs cleanup on internal objects
 */
Request.prototype.destroy = function () {
	this.headers = null;
	for (var i = 0; i < this.cookies.length; i++) {
		this.cookies[i].destroy();
		this.cookies[i] = null;
	}
	this.cookies = null;
	this.unwatch("querystring");
	this.querystring = null;
	this.unwatch("form");
	this.form = null;
	this.path = null;
	this.raw = null;
	this.rawHeaders = null;
	this.rawQuerystring = null;
	this.rawBody = null;
}

